<?php

// $Id: activerecord.php 2670 2009-11-18 07:16:09Z firzen $

/**
 * 定义 QDB_ActiveMongo_Abstract 类
 *
 * @link http://qeephp.com/
 * @copyright Copyright (c) 2006-2009 Qeeyuan Inc. {@link http://www.qeeyuan.com}
 * @license New BSD License {@link http://qeephp.com/license/}
 * @version $Id: activerecord.php 2670 2009-11-18 07:16:09Z firzen $
 * @package orm
 */

/**
 * QDB_ActiveMongo_Abstract 类实现了 Active Record 模式
 *
 * @author YuLei Liao <liaoyulei@qeeyuan.com>
 * @version $Id: activerecord.php 2670 2009-11-18 07:16:09Z firzen $
 * @package orm
 */
abstract class QDB_ActiveMongo_Abstract implements QDB_ActiveRecord_Callbacks, ArrayAccess {

    /**
     * 对象所有属性的值
     *
     * @var array
     */
    protected $_props;
    /**
     * 当前 ActiveRecord 对象的类名称
     *
     * @var string
     */
    protected $_class_name;
    /**
     * 对象的 ID
     *
     * 如果对象的 ID 是由多个属性组成，则 $_id 是一个由多个属性值组成的名值对。
     *
     * @var mixed
     */
    protected $_id = false;
    /**
     * ActiveRecord 继承类使用的 Meta 对象
     *
     * @var QDB_ActiveMongo_Meta
     */
    protected static $_meta;
    /**
     * 指示对象的哪些属性已经做了修改
     *
     * @var array
     */
    private $_changed_props = array();
    /**
     * 保存对象的属性第一次修改前值
     *
     * @var array
     */
    private $_changed_values = array();
    /**
     * 指示对象是否对应数据库中的一条记录
     *
     * @var boolean
     */
    private $_is_new_record = true;
    /**
     * 异常捕捉器
     *
     * @var array
     * @access private
     */
    public $__exception_trap = array();
    public $_connect;

    /**
     * 构造函数
     *
     * @param array|object $data 包含数据的名值对
     * @param int $names_style 名值对的键名风格
     * @param boolean $from_storage 是否从存储器载入数据
     */
    function __construct($data = null, $names_style = QDB::PROP, $from_storage = false) {
        $this->_class_name = get_called_class($this);
        $this->_class_name = get_class($this);
        $this->_db = self::getConn("mongo");
        $this->collection = $this->_db->{$this->_collection_name};
        
        // 触发 after_initialize 事件
        $this->_after_initialize();
        $this->_event(self::AFTER_INITIALIZE);
        $this->_after_initialize_post();
    }

    static function getConn($dsn_name) {
        static $conn;
        if (is_null($this->_connect)) {
            $dsn = Q::ini('db_dsn_pool/' . $dsn_name);
            $this->_connect = new Mongo("mongodb://{$dsn['host']}:{$dsn['port']}");
        }
        return $this->_connect;
    }

    // string getCollectionName() {{{
    /**
     *  Get Collection Name, by default the class name,
     *  but you it can be override at the class itself to give
     *  a custom name.
     *
     *  @return string Collection Name
     */
    protected function getCollectionName() {
        if (isset($this)) {
            return strtolower(get_class($this));
        } else {
            return strtolower(get_called_class());
        }
    }

    // }}}

    /**
     * 获得对象的 ID（既对象在数据库中的主键值）
     *
     * 如果对象的 ID 是由多个属性组成，则 id() 方法会返回一个数组。
     *
     * @param boolean $cached 默认返回缓存值
     *
     * @return mixed
     */
    function id($cached = true) {
        if ($cached && $this->_id !== false) {
            return $this->_id;
        }

        $id = array();
        foreach (self::$_meta[$this->_class_name]->idname as $name) {
            $id[$name] = $this->{$name};
        }

        if (count($id) == 1) {
            $id = reset($id);
        }

        $this->_id = $id;
        return $id;
    }

    /**
     * 获得对象的 ID 属性名（对象在数据库中的主键字段名）
     *
     * 如果对象的 ID 是由多个属性组成，则 idname() 方法会返回一个包含多个属性名的数组。
     *
     * @return string|array
     */
    function idname() {
        if (self::$_meta[$this->_class_name]->idname_count > 1) {
            return self::$_meta[$this->_class_name]->idname;
        } else {
            return reset(self::$_meta[$this->_class_name]->idname);
        }
    }

    /**
     * 确定对象是否对应数据库中的一条记录
     *
     * @return boolean
     */
    function isNewRecord() {
        return $this->_is_new_record;
    }

    /**
     * 返回当前对象的元信息对象
     *
     * @return QDB_ActiveMongo_Meta
     */
    function getMeta() {
        return self::$_meta[$this->_class_name];
    }

    /**
     * 保存对象到数据库
     *
     * @param int $recursion 保存操作递归到多少层
     * @param string $save_method 保存对象的方法
     *
     * @return QDB_ActiveMongo_Abstract 连贯接口
     */
    function save($recursion = 99, $save_method = 'save') {
        $inherit_type_field = self::$_meta[$this->_class_name]->inherit_type_field;
        if ($inherit_type_field && empty($this->_props[$inherit_type_field])) {
            $this->_props[$inherit_type_field] = $this->_class_name;
        }

        $this->_before_save();
        $this->_event(self::BEFORE_SAVE);
        $this->_before_save_post();

        try {
            switch (strtolower($save_method)) {
                case 'create':
                    $exception_event = self::CREATE_EXCEPTION;
                    $this->_create($recursion);
                    break;
                case 'update':
                    $exception_event = self::UPDATE_EXCEPTION;
                    $this->_update($recursion);
                    break;
                case 'replace':
                    $exception_event = self::REPLACE_EXCEPTION;
                    $this->_replace($recursion);
                    break;
                case 'save':
                default:
                    if ($this->isNewRecord()) {
                        try {
                            $exception_event = self::CREATE_EXCEPTION;
                            $this->_create($recursion);
                        } catch (QDB_Exception_DuplicateKey $ex) {
                            $exception_event = self::UPDATE_EXCEPTION;
                            $this->_update($recursion);
                        }
                    } else {
                        $exception_event = self::UPDATE_EXCEPTION;
                        $this->_update($recursion);
                    }
            }

            $this->_after_save();
            $this->_event(self::AFTER_SAVE);
            $this->_after_save_post();
        } catch (Exception $ex) {
            if (!empty($this->__exception_trap[$exception_event])) {
                foreach ($this->__exception_trap[$exception_event] as $callback) {
                    array_unshift($callback[1], $this);
                    array_push($callback[1], $ex);
                    call_user_func_array($callback[0], $callback[1]);
                }
            }

            throw $ex;
        }

        $this->_id = false; // 清除缓存
        return $this;
    }

    /**
     * 返回当前对象的一个复制品
     *
     * 返回的复制品没有 ID 值，因此在保存时将会创建一个新记录。
     * __clone() 操作仅限当前对象的属性，对于关联的对象不会进行克隆。
     *
     * @return QDB_ActiveMongo_Abstract 复制的 ActiveRecord 对象
     */
    function __clone() {
        foreach (self::$_meta[$this->_class_name]->idname as $name) {
            unset($this->$name);
        }

        $this->_id = false; // 清除缓存
        foreach ($this->meta()->props as $prop_name => $tmp) {
            $this->_changed_props[$prop_name] = true;
        }
        $this->_is_new_record = true;
        $this->__exception_trap = array();
    }

    /**
     * 判断对象是否有特定的属性
     *
     * @return boolean
     */
    function hasProp($prop_name) {
        return isset(self::$_meta[$this->_class_name]->props[$prop_name]);
    }

    /**
     * 将对象恢复到修改前的状态
     */
    function reset() {
        $this->reload();
    }

    /**
     * 从数据库重新读取当前对象的属性，不影响关联的对象
     */
    function reload() {
        if (self::$_meta[$this->_class_name]->idname_count > 1) {
            // 复合主键
            $where = $this->id();
        } else {
            // 单一主键
            $where = array(reset(self::$_meta[$this->_class_name]->idname) => $this->id());
        }

        $row = self::$_meta[$this->_class_name]->find($where)->asArray()->recursion(0)->query();
        $this->changeProps($row, QDB::FIELD, null, true);
    }

    /**
     * 销毁对象对应的数据库记录
     */
    function destroy() {
        $id = $this->id(false); // 不使用缓存
        if (empty($id)) {
            throw new QDB_ActiveMongo_DestroyWithoutIdException($this);
        }

        // 引发 before_destroy 事件
        $this->_before_destroy();
        $this->_event(self::BEFORE_DESTROY);
        $this->_before_destroy_post();

        // 处理关联的对象
        $meta = self::$_meta[$this->_class_name];
        /* @var $meta QDB_ActiveMongo_Meta */
        foreach ($meta->associations as $assoc) {
            $assoc->onSourceDestroy($this);
        }

        // 确定删除当前对象的条件
        if ($meta->idname_count > 1) {
            $where = $id;
        } else {
            $where = array(reset($meta->idname) => $id);
        }

        // 从数据库中删除当前对象
        $ret = $meta->table->delete($where);

        // 引发 after_destroy 事件
        $this->_after_destroy();
        $this->_event(self::AFTER_DESTROY);
        $this->_after_destroy_post();
        return $ret;
        //$this->_id = false; // 清除缓存
        // destroy() 并不改变主键值，无需更改 $this->_id 属性
    }

    /**
     * 批量设置对象的属性值
     *
     * 如果指定了 $attr_accessible 参数，则会忽略 ActiveRecord 类的 attr_accessible 和 attr_protected 设置。
     *
     * @param array|object $arr 名值对数组
     * @param int $names_style 键名是属性名还是字段名
     * @param array|string $attr_accessible 指定哪些属性允许设置
     * @param boolean $_from_storage 内部参数
     * @param boolean $_ignore_readonly 内部参数
     *
     * @return QDB_ActiveMongo_Abstract 连贯接口
     */
    function changeProps($arr, $names_style = QDB::PROP, $attr_accessible = null, $_from_storage = false, $_ignore_readonly = false) {
        $meta = self::$_meta[$this->_class_name];
        /* @var $meta QDB_ActiveMongo_Meta */

        if ($attr_accessible) {
            $attr_accessible = array_flip(Q::normalize($attr_accessible));
            $check_attr_accessible = true;
        } else {
            $check_attr_accessible = !empty($meta->attr_accessible);
            $attr_accessible = $meta->attr_accessible;
        }

        // 将数组赋值给对象属性
        foreach ($arr as $prop_name => $value) {
            if ($names_style == QDB::FIELD) {
                if (!isset($meta->fields2props[$prop_name])) {
                    continue;
                }
                $prop_name = $meta->fields2props[$prop_name];
            } elseif (!isset($meta->props[$prop_name])) {
                continue;
            }

            if ($_from_storage) {
                if ($meta->props[$prop_name]['virtual']) {
                    $this->{$prop_name} = $value;
                    unset($this->_changed_props[$prop_name]);
                } else {
                    $this->_props[$prop_name] = is_null($value) ? NULL : self::_typed($value, $meta->props[$prop_name]['ptype']);
                }
            } else {
                if ($check_attr_accessible) {
                    if (!isset($attr_accessible[$prop_name])) {
                        continue;
                    }
                } elseif (isset($meta->attr_protected[$prop_name])) {
                    continue;
                }

                if ($_ignore_readonly) {
                    $this->changePropForce($prop_name, $value);
                } else {
                    $this->{$prop_name} = $value;
                }
            }
        }

        return $this;
    }

    /**
     * 强制改变一个属性的值，忽略属性的 readonly 设置
     *
     * @param string $prop_name 要改变的属性名
     * @param mixed $prop_value 属性的值
     *
     * @return QDB_ActiveMongo_Abstract 连贯接口
     */
    function changePropForce($prop_name, $prop_value) {
        $meta = self::$_meta[$this->_class_name];
        /* @var $meta QDB_ActiveMongo_Meta */
        if (!isset($meta->props[$prop_name])) {
            return $this;
        }

        try {
            $ro = $meta->props[$prop_name]['readonly'];
            self::$_meta[$this->_class_name]->props[$prop_name]['readonly'] = false;
            $this->{$prop_name} = $prop_value;
            self::$_meta[$this->_class_name]->props[$prop_name]['readonly'] = $ro;
        } catch (Exception $ex) {
            self::$_meta[$this->_class_name]->props[$prop_name]['readonly'] = $ro;
            throw $ex;
        }

        return $this;
    }

    /**
     * 确认对象或指定的对象属性是否已经被修改
     *
     * @param string|array $props_name
     *
     * @return boolean
     */
    function changed($props_name = null) {
        if (is_null($props_name)) {
            return!empty($this->_changed_props);
        }

        $props_name = Q::normalize($props_name);
        foreach ($props_name as $prop_name) {
            if (isset($this->_changed_props[$prop_name])) {
                return true;
            }
        }
        return false;
    }

    /**
     * 将指定的属性设置为“脏”状态
     *
     * @param string|array $props_name
     *
     * @return QDB_ActiveMongo_Abstract 连贯接口
     */
    function willChanged($props_name) {
        $props_name = Q::normalize($props_name);
        foreach ($props_name as $prop_name) {
            if (!isset(self::$_meta[$this->_class_name]->props[$prop_name])) {
                continue;
            }
            $this->_changed_props[$prop_name] = $prop_name;
        }

        return $this;
    }

    /**
     * 获得修改过的属性
     *
     * @return array
     */
    function changes() {
//        return array_merge($this->_changed_props, $this->_changed_values);
        return $this->_changed_props;
    }

    /**
     * 清除所有属性或指定的“脏”状态
     *
     * @param string|array $props
     *
     * @return QDB_ActiveMongo_Abstract 连贯接口
     */
    function cleanChanges($props = null) {
        if ($props) {
            $props = Q::normalize($props);
            foreach ($props as $prop) {
                unset($this->_changed_props[$prop]);
            }
        } else {
            $this->_changed_props = array();
        }
        return $this;
    }

    /**
     * 获得包含对象所有属性的数组  
     *
     * @param int|array $recursion  $recursion为array时 只转换第一级关联的指定关联
     * @param int $names_style
     *
     * @return array
     */
    function toArray($recursion = 0, $names_style = QDB::PROP) {
        $data = array();
        $only_links = array();

        if (!empty($recursion) && !is_int($recursion)) { // array(link1,link2);
            $only_links = Q::normalize($recursion);
            $recursion = 1;
        }
        //增加真实属性值! 这些属性在持久化的时候会被忽略
        $real_props = get_object_vars($this);
        foreach ($real_props as $_k => $_p) {
            if (strpos($_k, "_") === 0) {  //私有属性不转化
                unset($real_props[$_k]);
            }
        }
        $data = array_merge($data, $real_props);
        //end!

        $meta = self::$_meta[$this->_class_name];
        /* @var $meta QDB_ActiveMongo_Meta */
        foreach ($meta->props as $prop_name => $config) {
            if ($names_style == QDB::PROP) {
                $name = $prop_name;
            } else {
                $name = $meta->props2fields[$prop_name];
            }

            if ($config['assoc']) {
                if ($recursion > 0 /* && isset($this->_props[$prop_name]) */) {
                    if (!empty($only_links) && !in_array($prop_name, $only_links)) {
                        continue;
                    }
                    if ($config['assoc'] == QDB::HAS_ONE || $config['assoc'] == QDB::BELONGS_TO) {
                        $data[$name] = $this->{$prop_name}->toArray($recursion - 1, $names_style);
                    } else {
                        $data[$name] = $this->{$prop_name}->getAll()->toArray($recursion - 1, $names_style);
                    }
                }
            } elseif ($config['virtual'] && isset($this->{$prop_name})) {
                $data[$name] = $this->{$prop_name};
            } elseif ($config['virtual'] && empty($config['getter'])) {
                continue;
            } else {
                $data[$name] = $this->{$prop_name};
            }
        }
        return $data;
    }

    /**
     * 返回对象所有属性的 JSON 字符串
     *
     * @param int $recursion
     * @param int $names_style
     *
     * @return string
     */
    function toJSON($recursion = 0, $names_style = QDB::PROP) {
        return json_encode($this->toArray($recursion, $names_style));
    }

    /**
     * 魔法方法，实现对象属性值的读取
     *
     * @param string $prop_name
     *
     * @return mixed
     */
    function __get($prop_name) {
        if (!isset(self::$_meta[$this->_class_name]->props[$prop_name])) {
            throw new QDB_ActiveMongo_UndefinedPropException($this->_class_name, $prop_name);
        }

        $config = self::$_meta[$this->_class_name]->props[$prop_name];
        if (!empty($config['getter'])) {
            // 如果指定了属性的 getter，则通过 getter 方法来获得属性值
            list($callback, $custom_parameters) = $config['getter'];
            if (!is_array($callback)) {
                $callback = array($this, $callback);
                $args = array($prop_name, $custom_parameters, & $this->_props);
            } else {
                $args = array($this, $prop_name, $custom_parameters, & $this->_props);
            }
            return call_user_func_array($callback, $args);
        }

        if (!isset($this->_props[$prop_name]) && $config['assoc']) {
            // 如果属性是一个关联，并且没有值，则通过 QDB_ActiveMongo_Meta::relatedObjects() 获得关联对象
            $this->_props[$prop_name] = self::$_meta[$this->_class_name]->relatedObjects($this, $prop_name);
        }

        return $this->_props[$prop_name];
    }

    /**
     * 魔法方法，实现对象属性的设置
     *
     * @param string $prop_name
     * @param mixed $value
     */
    function __set($prop_name, $value) {
        $meta = self::$_meta[$this->_class_name];
        /* @var $meta QDB_ActiveMongo_Meta */

        if (!isset($meta->props[$prop_name])) {
//            throw new QDB_ActiveMongo_UndefinedPropException($this->_class_name, $prop_name);
        }

        $config = $meta->props[$prop_name];
        if ($config['readonly']) {
            throw new QDB_ActiveMongo_ChangingReadonlyPropException($this->_class_name, $prop_name);
        }
        //FIXME 虛擬屬性可以任意指定,只是不入庫
        if ($config['virtual']) {
            $this->_props[$prop_name] = null;
        }

        if (!empty($config['setter'])) {
            // 如果指定了属性的 setter，则通过 setter 方法来修改属性值
            list($callback, $custom_parameters) = $config['setter'];
            if (!is_array($callback)) {
                $callback = array($this, $callback);
                $args = array($value, $prop_name, $custom_parameters, & $this->_props);
            } else {
                $args = array($this, $value, $prop_name, $custom_parameters, & $this->_props);
            }
            return call_user_func_array($callback, $args);
        }

        if ($config['assoc']) {
            // 在指定关联对象时，要进行类型检查
            if ($config['assoc'] == QDB::HAS_ONE || $config['assoc'] == QDB::BELONGS_TO) {
                if ($value instanceof $config['assoc_class']) {
                    $this->_props[$prop_name] = $value;

                    if ($config['assoc'] == QDB::BELONGS_TO) {
                        $assoc = $meta->assoc($prop_name);
                        $this->_props[$assoc->source_key] = $value[$assoc->target_key];
                        $this->_changed_props[$assoc->source_key] = $assoc->source_key;
                    }
                } else {
                    throw new QDB_ActiveMongo_SettingPropTypeMismatchException($this->_class_name,
                            $prop_name, $config['assoc_class'],
                            gettype($value));
                }
            } else {
                if (is_array($value)) {
                    $this->_props[$prop_name] = QColl::createFromArray($value, $config['assoc_class']);
                } elseif ($value instanceof Iterator) {
                    $this->_props[$prop_name] = $value;
                } else {
                    throw new QDB_ActiveMongo_SettingPropTypeMismatchException($this->_class_name, $prop_name, 'Iterator', gettype($value));
                }
            }

            $this->_changed_props[$prop_name] = $prop_name;
        } elseif (is_object($value) && $value instanceof QDB_Expr) {
            $this->_props[$prop_name] = $value;
            $this->_changed_props[$prop_name] = $prop_name;
        } elseif ($this->_props[$prop_name] !== $value) {
            //@edited 保存基本属性第一次修改前的值
            if (!isset($this->_changed_values[$prop_name])) {
                $this->_changed_values[$prop_name] = $this->_props[$prop_name];
            }

            $this->_props[$prop_name] = self::_typed($value, $config['ptype']);
            $this->_changed_props[$prop_name] = $prop_name;
        }
    }

    /**
     * 魔法方法，实现对 isset() 的支持
     *
     * @param string $prop_name
     *
     * @return boolean
     */
    function __isset($prop_name) {
        return array_key_exists($prop_name, $this->_props);
    }

    /**
     * 魔法方法，用于调用行为插件为对象添加的方法
     *
     * @param string $method
     * @param array $args
     *
     * @return mixed
     */
    function __call($method, array $args) {
        if (isset(self::$_meta[$this->_class_name]->methods[$method])) {
            $callback = self::$_meta[$this->_class_name]->methods[$method];
            foreach ($args as $arg) {
                array_push($callback[1], $arg);
            }
            array_unshift($callback[1], $this);
            return call_user_func_array($callback[0], $callback[1]);
        }

        // getXX() 和 setXX() 方法
        $prefix = substr($method, 0, 3);
        if ($prefix == 'get') {
            $prop_name = strtolower(substr($method, 3));
            return $this->{$prop_name};
        } elseif ($prefix == 'set') {
            $prop_name = strtolower(substr($method, 3));
            $this->{$prop_name} = reset($args);
            return $this;
        }


        //@edited 实现关联同名的方法 $user->posts('post_id > ?' ,1)->getAll();
        if (self::$_meta[$this->_class_name]->hasAssoc($method)) {
            $config = self::$_meta[$this->_class_name]->props[$method];
            if ($config['assoc'] != QDB::MANY_TO_MANY) {
                $assoc = self::$_meta[$this->_class_name]->assoc($method)->init();
                $source_key_value = $this->{$assoc->source_key};
                $target_meta = self::$_meta[$this->_class_name]->instance($config['assoc_class']);

                /* @var $select QDB_Select */
                $select = $target_meta->find(array($assoc->target_key => $source_key_value));
                if (strlen($assoc->source_key_2nd) && strlen($assoc->target_key_2nd)) {
                    $select->where(array($assoc->target_key_2nd => $this->{$assoc->source_key_2nd}));
                }

                if (!empty($assoc->on_find_where)) {
                    call_user_func_array(array($select, 'where'), $assoc->on_find_where);
                }
                if (!empty($assoc->on_find_order)) {
                    $select->order($assoc->on_find_order);
                }
                if ($assoc->on_find === 'all' || $assoc->on_find === true) {
                    $select->all();
                } elseif (is_int($assoc->on_find)) {
                    $select->limit(0, $assoc->on_find);
                } elseif (is_array($assoc->on_find)) {
                    $select->limit($assoc->on_find[0], $assoc->on_find[1]);
                }

                if (!empty($args)) {
                    call_user_func_array(array($select, 'where'), $args);
                }

                if ($assoc->one_to_one) {
                    return $select->one();
//                    $objects = $select->query();
//                    if (count($objects)) {
//                        return (is_object($objects)) ? $objects->first() : reset($objects);
//                    } else {
//                        return $target_meta->newObject();
//                    }
                } else {
                    return $select->asColl();
                }
                return $select;
            }
        }
        //@edited END

        throw new QDB_ActiveMongo_CallToUndefinedMethodException($this->_class_name, $method);
    }

    /**
     * ArrayAccess 接口方法
     *
     * @param string $prop_name
     *
     * @return boolean
     */
    function offsetExists($prop_name) {
        return array_key_exists($prop_name, $this->_props);
    }

    /**
     * ArrayAccess 接口方法
     *
     * @param string $prop_name
     * @param mixed $value
     */
    function offsetSet($prop_name, $value) {
        $this->{$prop_name} = $value;
    }

    /**
     * ArrayAccess 接口方法
     *
     * @param string $prop_name
     *
     * @return boolean
     */
    function offsetGet($prop_name) {
        return $this->{$prop_name};
    }

    /**
     * ArrayAccess 接口方法
     *
     * @param string $prop_name
     */
    function offsetUnset($prop_name) {
        $this->{$prop_name} = null;
    }

    /**
     * 用于 QColl 的回调方法
     */
    static function _qcoll_callback() {
        return array('tojson' => 'multiToJSON');
    }

    /**
     * 将多个 ActiveRecord 对象转换为 JSON 字符串
     *
     * @param array $objects
     * @param int $recursion
     * @param int $names_style
     *
     * @return string
     */
    static function multiToJSON(array $objects, $recursion = 0, $names_style = QDB::PROP) {
        $arr = array();
        while (list(, $obj) = each($objects)) {
            $arr[] = $obj->toArray($recursion, $names_style);
        }
        return json_encode($arr);
    }

    /**
     * 调用 ActiveRecord 对象的动态方法
     *
     * @param string $method
     *
     * @return mixed
     */
    protected function _method($method) {
        $args = func_get_args();
        array_shift($args);
        return $this->__call($method, $args);
    }

    /**
     * 触发事件
     *
     * @param int $event
     */
    protected function _event($event) {
        $meta = self::$_meta[$this->_class_name];
        if (empty($meta->callbacks[$event])) {
            return;
        }

        foreach ($meta->callbacks[$event] as $callback) {
            array_unshift($callback[1], $this);
            call_user_func_array($callback[0], $callback[1]);
        }
    }

    /**
     * 在数据库中创建对象
     *
     * @param int $recursion
     */
    protected function _create($recursion = 99) {
        $meta = self::$_meta[$this->_class_name];
        /* @var $meta QDB_ActiveMongo_Meta */

        // 根据 create_autofill 设置对属性进行填充
        $this->_autofill('create');

        // 引发 before_create 事件
        $this->_before_create();
        $this->_event(self::BEFORE_CREATE);
        $this->_before_create_post();

        // 进行 create 验证
        $this->validate('create', true);

        // 特别处理 BELONGS_TO 关联
        foreach ($meta->belongsto_props as $prop_name => $assoc) {
            /* @var $assoc QDB_ActiveMongo_Association_BelongsTo */
            $mapping_name = $assoc->mapping_name;
            $source_key = $assoc->source_key;

            if (empty($this->_props[$mapping_name])) {
                if (empty($this->_props[$source_key])) {
                    if ($this->_props[$source_key] === $meta->props[$source_key]['default_value']
                            && !is_null($meta->props[$source_key]['default_value'])) {
                        $this->changePropForce($source_key, $meta->props[$source_key]['default_value']);
                    } else {
                        // 如果BELONGS TO关联设置了允许空值，则跳过抛出异常
                        if (empty($assoc->source_meta->props[$mapping_name]['assoc_params']['skip_empty'])) {
                            throw new QDB_ActiveMongo_ExpectsAssocPropException($this->_class_name, $mapping_name);
                        }
                    }
                }
            } else {
                $belongsto = $this->_props[$mapping_name];
                /* @var $belongsto QDB_ActiveMongo_Abstract */
                $this->changePropForce($source_key, $belongsto->{$assoc->target_key});
            }
        }

        // 准备要保存到数据库的数据
        $save_data = array();
        foreach ($this->_props as $prop_name => $value) {
            if (isset($meta->create_reject[$prop_name]) || $meta->props[$prop_name]['virtual']) {
                continue;
            }
            $save_data[$meta->props2fields[$prop_name]] = $value;
        }

        // 将名值对保存到数据库
        $pk = $meta->table->insert($save_data, true);

        // 将获得的主键值指定给对象
        foreach ($pk as $field_name => $field_value) {
            $this->_props[$meta->props2fields[$field_name]] = $field_value;
        }

        // 遍历关联的对象，并调用对象的save()方法
        foreach ($meta->associations as $prop => $assoc) {
            if ($assoc->type == QDB::BELONGS_TO || !isset($this->_props[$prop])) {
                continue;
            }

            /* @var $assoc QDB_ActiveMongo_Association_Abstract */

            $assoc->init();
            $source_key_value = $this->{$assoc->source_key};

            if (strlen($source_key_value) == 0) {
                throw new QDB_ActiveMongo_ExpectsAssocPropException($this->_class_name, $assoc->source_key);
            }

            $assoc->onSourceSave($this, $recursion - 1);
        }
        // 成功保存到数据库后已经不是新记录了
        $this->_is_new_record = false;
        // 引发after_create事件
        $this->_after_create();
        $this->_event(self::AFTER_CREATE);
        $this->_after_create_post();

        // 清除所有属性的“脏”状态
        $this->_changed_props = array();
    }

    /**
     * 更新对象到数据库
     *
     * @param int $recursion
     */
    protected function _update($recursion = 99) {
        $meta = self::$_meta[$this->_class_name];
        /* @var $meta QDB_ActiveMongo_Meta */

        /**
         * 仅在有属性更新时才引发 update 事件，并进行更新操作
         */
        if (!empty($this->_changed_props)) {
            // 根据 update_autofill 设置对属性进行填充
            $this->_autofill('update');

            // 引发 before_update 事件
            $this->_before_update();
            $this->_event(self::BEFORE_UPDATE);
            $this->_before_update_post();

            // 进行 update 验证
            $this->validate('update', true);

            // 准备要更新到数据库的数据
            $save_data = array();
            foreach ($this->_props as $prop_name => $value) {
                // 根据 update_reject 过滤掉不允许更新的属性
                if (isset($meta->update_reject[$prop_name])
                        || ($meta->props[$prop_name]['virtual'] && !isset($meta->table_meta[$prop_name]))) {
                    continue;
                }
                // 只有指定为脏状态的属性才更新到数据库
                if (isset($this->_changed_props[$prop_name])) {
                    $save_data[$meta->props2fields[$prop_name]] = $value;
                }
            }

            if (!empty($save_data)) {
                // 确定更新条件
                $conditions = array();
                foreach ($meta->table->getPK() as $field_name) {
                    $prop_name = $meta->fields2props[$field_name];
                    unset($save_data[$field_name]);
                    $conditions[$field_name] = $this->_props[$prop_name];
                }
                if (!empty($save_data)) {
                    foreach ((array) $save_data as $field => $value) {
                        //只有数字型字段需要判断
                        $ptypes = array("int1", "int2", "int3", "int4", "float", "double", "timestamp");
                        if (!isset($meta->props[$field]['update_check'])
                                || !isset($this->_changed_values[$field])   // 判断update属性 gte || gt
                                || !isset($meta->props[$field])
                                || !in_array($meta->props[$field]['ptype'], $ptypes)) {
                            continue;
                        }
                        $op = $meta->props[$field]['update_check'];
                        $op = ($op == "gt_zero") ? ">" : ">=";
                        $change = $this->_changed_values[$field] - $value;
                        if ($change < 0) {
                            continue;
                        }
                        $save_data[$field] = new QDB_Expr("$field - {$change}");
                        $conditions[] = " `{$field}` $op $change";
                    }

                    //@edited 增加对+= -=表达式的处理
                    /**
                      foreach((array)$save_data as $_f =>$value){
                      if($value instanceof QDB_Expr){
                      $expr = $value->__toString();
                      $op = (strpos($expr,"+=")===0)?"+":"";
                      $op = (strpos($expr,"-=")===0)?"-":$op;
                      if(!empty($op)){
                      $_v = substr($expr,2)*1;
                      if(is_numeric($_v) && $_v<0){
                      $_v = $_v*-1;
                      switch ($op){
                      case "+":
                      $op="-";
                      break;
                      case "-":
                      $op="+";
                      break;
                      }
                      }
                      $save_data[$_f] = new QDB_Expr("$_f".$op.$_v);
                      //当前状态值; $this->props[$k]-=2;
                      if($op==="-"){
                      $conditions[]=" `{$_f}` >= {$_v}";
                      }
                      }
                      }
                      }
                     * */
                    //TODO 临时实现,再考虑!!!!  $save_data 可以是QDB_Expr 但是目前只能是一个QDB_Expr对象,不能同其他类型的参数混合,
                    /**
                      $exprs = array();
                      foreach((array)$save_data as $_f =>$value){
                      if($value instanceof QDB_Expr){
                      $expr = $value->__toString();
                      $op = (strpos($expr,"+=")===0)?"+":"";
                      $op = (strpos($expr,"-=")===0)?"-":$op;
                      if(!empty($op)){
                      $_v = substr($expr,2)*1;
                      if(is_numeric($_v) && $_v<0){
                      $_v = $_v*-1;
                      switch ($op){
                      case "+":
                      $op="-";
                      break;
                      case "-":
                      $op="+";
                      break;
                      }
                      }
                      if($op==="-"){
                      $conditions[]=" `{$_f}` >= {$_v}";
                      }
                      $expr = "`{$_f}` = `{$_f}` {$op} {$_v}";
                      }
                      $exprs[] = $expr ;
                      unset($save_data[$_f]);
                      }else{
                      $exprs[] = "`{$_f}` = {$value}";
                      }
                      }
                      $save_data = new QDB_Expr(implode(" , ", $exprs));
                     * */
                    // 将名值对保存到数据库
                    $affected_num = $meta->table->update($save_data, $conditions);
                    if (!($affected_num > 0)) {//更新失败,没有符合条件的数据
                        $this->status = $this->_changed_values[$field];
                        throw new QException(__('property "%s" update failed  on object "%s" instance.', $field, get_class($this)));
                    }
                    //更新操作没有成功的情况 无符合条件记录, ||从底层抛出个异常??
                }
            }
        }

        // 遍历关联的对象，并调用对象的save()方法
        foreach ($meta->associations as $prop => $assoc) {
            if (!isset($this->_props[$prop])) {
                continue;
            }

            $assoc->init();
            $source_key_value = $this->{$assoc->source_key};

            if (strlen($source_key_value) == 0) {
                throw new QDB_ActiveMongo_ExpectsAssocPropException($this->_class_name, $assoc->source_key);
            }

            $assoc->onSourceSave($this, $recursion - 1);
        }

        // 引发 after_update 事件
        $this->_after_update();
        $this->_event(self::AFTER_UPDATE);
        $this->_after_update_post();

        // 清除所有属性的“脏”状态
        $this->_changed_props = array();
        $this->_changed_values = array();
    }

    /**
     * 替换数据库中的对象，如果不存在则创建新记录
     *
     * @param int $recursion
     */
    protected function _replace($recursion = 99) {
        if ($this->isNewRecord() || $this->changed()) {
            if ($this->isNewRecord()) {
                try {
                    // 如果是尚未保存到数据库的记录，则创建新记录
                    $this->_create($recursion);
                } catch (QDB_Exception_DuplicateKey $ex) {
                    $this->_update($recursion);
                }
            } else {
                $this->_update($recursion);
            }
        }
    }

    /**
     * 对当前对象的属性进行自动填充
     *
     * @param string $mode
     */
    protected function _autofill($mode) {
        $meta = self::$_meta[$this->_class_name];
        /* @var $meta QDB_ActiveMongo_Meta */
        $fill_props = ($mode == 'create') ? $meta->create_autofill : $meta->update_autofill;

        foreach ($fill_props as $prop => $fill) {
            if ($fill === self::AUTOFILL_DATETIME) {
                $this->_props[$prop] = date('Y-m-d H:i:s', CURRENT_TIMESTAMP);
            } elseif ($fill === self::AUTOFILL_TIMESTAMP) {
                $this->_props[$prop] = intval(CURRENT_TIMESTAMP);
            } elseif ($fill === self::AUTOFILL_DATE) {
                $this->_props[$prop] = date('Y-m-d', CURRENT_TIMESTAMP);
            } elseif ($fill === self::AUTOFILL_TIME) {
                $this->_props[$prop] = date('H:i:s', CURRENT_TIMESTAMP);
            } elseif (!is_array($fill)) {
                $this->_props[$prop] = $fill;
            } else {
                $this->_props[$prop] = self::_typed(call_user_func($fill), $meta->props[$prop]['ptype']);
            }
            // 设置“脏”状态
            $this->_changed_props[$prop] = true;
        }
    }

    /**
     * 返回类型化以后的值
     *
     * @param mixed $value
     * @param string $ptype
     *
     * @return mixed
     */
    static protected function _typed($value, $ptype) {
        switch ($ptype) {
            case 'int1':
            case 'int2':
            case 'int3':
            case 'int4':
            case 'timestamp':
            case 'autoincr':
                return intval($value);
            case 'float':
            case 'double':
            case 'dec':
                return doubleval($value);
            case 'bool':
                return (bool) $value;
            case 'date':
            case 'datetime':
                return empty($value) ? null : $value;
        }

        return $value;
    }

    /**
     * 事件回调：开始验证之前
     */
    protected function _before_validate() {
        
    }

    protected function _before_validate_post() {
        
    }

    /**
     * 事件回调：为创建记录进行的验证开始之前
     */
    protected function _before_validate_on_create() {
        
    }

    protected function _before_validate_on_create_post() {
        
    }

    /**
     * 事件回调：为创建记录进行的验证完成之后
     */
    protected function _after_validate_on_create() {
        
    }

    protected function _after_validate_on_create_post() {
        
    }

    /**
     * 事件回调：为更新记录进行的验证开始之前
     */
    protected function _before_validate_on_update() {
        
    }

    protected function _before_validate_on_update_post() {
        
    }

    /**
     * 事件回调：为更新记录进行的验证完成之后
     */
    protected function _after_validate_on_update() {
        
    }

    protected function _after_validate_on_update_post() {
        
    }

    /**
     * 事件回调：验证完成之后
     */
    protected function _after_validate() {
        
    }

    protected function _after_validate_post() {
        
    }

    /**
     * 事件回调：保存记录之前
     */
    protected function _before_save() {
        
    }

    protected function _before_save_post() {
        
    }

    /**
     * 事件回调：保存记录之后
     */
    protected function _after_save() {
        
    }

    protected function _after_save_post() {
        
    }

    /**
     * 事件回调：创建记录之前
     */
    protected function _before_create() {
        
    }

    protected function _before_create_post() {
        
    }

    /**
     * 事件回调：创建记录之后
     */
    protected function _after_create() {
        
    }

    protected function _after_create_post() {
        
    }

    /**
     * 事件回调：更新记录之前
     */
    protected function _before_update() {
        
    }

    protected function _before_update_post() {
        
    }

    /**
     * 事件回调：更新记录之后
     */
    protected function _after_update() {
        
    }

    protected function _after_update_post() {
        
    }

    /**
     * 事件回调：删除记录之前
     */
    protected function _before_destroy() {
        
    }

    protected function _before_destroy_post() {
        
    }

    /**
     * 事件回调：删除记录之后
     */
    protected function _after_destroy() {
        
    }

    protected function _after_destroy_post() {
        
    }

    /**
     * 事件回调：对象构造之后
     */
    protected function _after_initialize() {
        
    }

    protected function _after_initialize_post() {
        
    }

    static function find() {
        $args = func_get_args();
        return self::instance($class)->find($args);
    }


}

